////////////////////////////////////////////////////////////////////////////
//
//  Crytek Engine Source File.
//  Copyright (C), Crytek Studios, 2002.
// -------------------------------------------------------------------------
//  File name:   terrain_water_quad.cpp
//  Version:     v1.00
//  Created:     28/8/2001 by Vladimir Kajalin
//  Compilers:   Visual Studio.NET
//  Description: Create and draw terrain water geometry (screen space grid, cycle buffers)
// -------------------------------------------------------------------------
//  History:
//
////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"

#include "terrain_water.h"
#include "CullBuffer.h"
#include "3dEngine.h"
#include "ObjMan.h"
#include "VisAreas.h"

ITimer *COcean::m_pTimer = 0;
CREWaterOcean *COcean::m_pOceanRE = 0;
uint32 COcean::m_nVisiblePixelsCount = ~0;

COcean::COcean(IMaterial * pMat)
{ 
	m_pRenderMesh = 0;
	m_pBottomCapRenderMesh = 0;

	memset(m_fRECustomData, 0, sizeof(m_fRECustomData));
	memset(m_fREOceanBottomCustomData, 0, sizeof(m_fREOceanBottomCustomData));

	m_pMaterial = pMat;
	m_fLastFov=0;
	m_fLastVisibleFrameTime = 0.0f;    

	m_pShaderOcclusionQuery = GetRenderer()->EF_LoadShader("OcclusionTest", 0);

	memset(m_pREOcclusionQueries, 0, sizeof(m_pREOcclusionQueries));

	m_nLastVisibleFrameId = 0;

	m_pBottomCapMaterial = GetMatMan()->LoadMaterial( "EngineAssets/Materials/Water/WaterOceanBottom", false );
	m_pFogIntoMat = GetMatMan()->LoadMaterial( "EngineAssets/Materials/Fog/OceanInto", false );
	m_pFogOutofMat = GetMatMan()->LoadMaterial( "EngineAssets/Materials/Fog/OceanOutof", false );
	m_pFogIntoMatLowSpec = GetMatMan()->LoadMaterial( "EngineAssets/Materials/Fog/OceanIntoLowSpec", false );
	m_pFogOutofMatLowSpec = GetMatMan()->LoadMaterial( "EngineAssets/Materials/Fog/OceanOutofLowSpec", false );

	for (int i = 0; i < RT_COMMAND_BUF_COUNT; i++)
	{
		m_pWVRE[i] = static_cast<CREWaterVolume*>( GetRenderer()->EF_CreateRE( eDATA_WaterVolume ) );
		if( m_pWVRE[i] )
		{
			m_pWVRE[i]->m_drawWaterSurface = false;
			m_pWVRE[i]->m_pParams = &m_wvParams[i];
			m_pWVRE[i]->m_pOceanParams = &m_wvoParams[i];
		}
	}

	m_pOceanRE = static_cast<CREWaterOcean*>( GetRenderer()->EF_CreateRE( eDATA_WaterOcean ) );

	m_nVertsCount = 0;
	m_nIndicesCount = 0;

	m_bOceanFFT = false;
}


COcean::~COcean()
{
	for(int32 x=0; x<CYCLE_BUFFERS_NUM; ++x)
	{
		if(m_pREOcclusionQueries[x])
			m_pREOcclusionQueries[x]->Release(true);
	}

	if( m_pRenderMesh )
		GetRenderer()->DeleteRenderMesh( m_pRenderMesh );
	if( m_pBottomCapRenderMesh )
		GetRenderer()->DeleteRenderMesh(m_pBottomCapRenderMesh);

	SAFE_RELEASE(m_pOceanRE);
	for (int i = 0; i < RT_COMMAND_BUF_COUNT; i++)
		SAFE_RELEASE(m_pWVRE[i]);
}

int32 COcean::GetMemoryUsage() 
{
	int32 nSize=0;

	nSize += sizeofVector(m_pMeshIndices);
	nSize += sizeofVector(m_pMeshVerts);
	nSize += sizeofVector(m_pBottomCapVerts);
	nSize += sizeofVector(m_pBottomCapIndices);

	return nSize;
}

void COcean::Update(const int32 nRecurseLevel, int32 nFrameID)
{
	FUNCTION_PROFILER_3DENGINE;

	C3DEngine * p3DEngine = (C3DEngine*)Get3DEngine();  
	IRenderer* pRenderer = GetRenderer();
	if(nRecurseLevel>0 ||  GetCVars()->e_WaterOcean == 0 || !m_pMaterial)
		return;

	int32 nFillThreadID = (int32) pRenderer->EF_Query(EFQ_MainThreadList);
	uint32 nBufID = nFrameID % CYCLE_BUFFERS_NUM;

	Vec3 vCamPos = GetCamera().GetPosition();
	float fWaterLevel = p3DEngine->GetWaterLevel();

	// No hardware FFT support
	m_bOceanFFT = false;
	if( ( pRenderer->GetFeatures() & (RFT_HW_VERTEXTEXTURES) && GetCVars()->e_WaterOceanFFT) && pRenderer->EF_GetShaderQuality( eST_Water ) >= eSQ_High )
		m_bOceanFFT = true;

	if(vCamPos.z < fWaterLevel)  
	{  
		// if camera is in indoors and lower than ocean level 
		// and exit portals are higher than ocean level - skip ocean rendering
		CVisArea * pVisArea = (CVisArea *)p3DEngine->GetVisAreaFromPos(vCamPos);
		if(pVisArea && !pVisArea->IsPortal())
		{
			for(int32 i=0; i<pVisArea->m_lstConnections.Count(); i++)
			{
				if(pVisArea->m_lstConnections[i]->IsConnectedToOutdoor() && pVisArea->m_lstConnections[i]->m_boxArea.min.z<fWaterLevel)
					break; // there is portal making ocean visible

				if(i==pVisArea->m_lstConnections.Count())
					return; // ocean surface is not visible 
			}
		}
	}

	bool bWaterVisible = IsVisible(nFrameID);
	float _fWaterPlaneSize = GetCamera().GetFarPlane();

	// Check if water surface occluded
	if( fabs(m_fLastFov - GetCamera().GetFov())<0.01f && GetCVars()->e_HwOcclusionCullingWater && (nRecurseLevel==0))
	{
		AABB boxOcean( Vec3( vCamPos.x-_fWaterPlaneSize, vCamPos.y-_fWaterPlaneSize,fWaterLevel ),
			Vec3( vCamPos.x+_fWaterPlaneSize, vCamPos.y+_fWaterPlaneSize,fWaterLevel ) );    

		if((!GetVisAreaManager()->IsOceanVisible() && GetCamera().IsAABBVisible_EM(boxOcean)) ||
			( GetVisAreaManager()->IsOceanVisible() && GetCamera().IsAABBVisible_E(boxOcean)))
		{
			// make element if not ready
			if(!m_pREOcclusionQueries[nBufID])
			{
				m_pREOcclusionQueries[nBufID] = (CREOcclusionQuery *)GetRenderer()->EF_CreateRE(eDATA_OcclusionQuery);
				m_pREOcclusionQueries[nBufID]->m_pRMBox = (CRenderMesh*)GetObjManager()->GetRenderMeshBox();
			}

			// get last test result
		//	if((m_pREOcclusionQueries[nFillThreadID][nBufID]->m_nCheckFrame - GetFrameID())<2)
			{
				COcean::m_nVisiblePixelsCount = m_pREOcclusionQueries[nBufID]->m_nVisSamples;
				if(COcean::m_nVisiblePixelsCount>16)
				{ 
					m_nLastVisibleFrameId = nFrameID;
					bWaterVisible = true;
				}
			}

			// request new test    
			m_pREOcclusionQueries[nBufID]->m_vBoxMin( boxOcean.min.x, boxOcean.min.y, boxOcean.min.z - 1.0f);
			m_pREOcclusionQueries[nBufID]->m_vBoxMax( boxOcean.max.x, boxOcean.max.y, boxOcean.max.z );

			m_pREOcclusionQueries[nBufID]->mfReadResult_Try(COcean::m_nVisiblePixelsCount);
			if(!m_pREOcclusionQueries[nBufID]->m_nDrawFrame || m_pREOcclusionQueries[nBufID]->HasSucceeded())	
			{
				SShaderItem pOccQuerySI(m_pShaderOcclusionQuery);
				CRenderObject *pObj = GetIdentityCRenderObject();
				if (!pObj)
					return;
				GetRenderer()->EF_AddEf(m_pREOcclusionQueries[nBufID], pOccQuerySI, pObj, EFSLIST_WATER_VOLUMES, 0); 
			}
		}
	}
	else
	{
		m_nLastVisibleFrameId = nFrameID;
		bWaterVisible = true;
	}

	if( bWaterVisible || vCamPos.z  < fWaterLevel )
	{
		m_p3DEngine->SetOceanRenderFlags( OCR_OCEANVOLUME_VISIBLE );

		// lazy mesh creation
		if( bWaterVisible )
		{
			Create();
		}
	}
}

void COcean::Create()
{
	IRenderer* pRenderer( GetRenderer() );

	// Calculate water geometry and update vertex buffers
	int32 nScrGridSizeX = 20 * GetCVars()->e_WaterTessellationAmount; 

	nScrGridSizeX = (GetCVars()->e_WaterTessellationAmountX && GetCVars()->e_WaterTessellationAmountY)? GetCVars()->e_WaterTessellationAmountX : nScrGridSizeX;
	int32 nScrGridSizeY = (GetCVars()->e_WaterTessellationAmountX && GetCVars()->e_WaterTessellationAmountY)? GetCVars()->e_WaterTessellationAmountY : nScrGridSizeX;
	
	// Store permanently the swath width
	static int32 swathWidth = 0;
	static bool bUsingFFT = false;
	static int32 bUseTessHW = 0;
	static ICVar*	pVarWaterTessellationHW = GetConsole()->GetCVar("r_WaterTessellationHW");
	int32 bUseWaterTessHW = pVarWaterTessellationHW && pVarWaterTessellationHW->GetIVal() != 0;

	if( !bUseWaterTessHW && m_bOceanFFT )
		nScrGridSizeX = nScrGridSizeY = 20 * 10; // for hi/very specs - use maximum tessellation

	// Generate screen space grid
	if( (m_bOceanFFT && bUsingFFT != m_bOceanFFT) || bUseTessHW != bUseWaterTessHW|| swathWidth != GetCVars()->e_WaterTessellationSwathWidth || !m_nVertsCount || !m_nIndicesCount ||  nScrGridSizeX * nScrGridSizeY != m_nPrevGridDim )
	{    
		m_nPrevGridDim = nScrGridSizeX * nScrGridSizeY;
		m_pMeshVerts.Clear();
		m_pMeshIndices.Clear();
		m_nVertsCount = 0;
		m_nIndicesCount = 0;

		bUsingFFT = m_bOceanFFT;
		bUseTessHW = bUseWaterTessHW;
		// Update the swath width
		swathWidth = GetCVars()->e_WaterTessellationSwathWidth;

		// Render ocean with screen space tessellation

		int32 nScreenY = GetRenderer()->GetHeight();
		int32 nScreenX = GetRenderer()->GetWidth();

		if(!nScreenY || !nScreenX)
		{
			return;
		}

		float fRcpScrGridSizeX = 1.0f / ((float) nScrGridSizeX - 1);
		float fRcpScrGridSizeY = 1.0f / ((float) nScrGridSizeY - 1);

		SVF_P3F_C4B_T2F tmp;
		Vec3 vv;
		vv.z = 0;

		m_pMeshVerts.reserve( nScrGridSizeX * nScrGridSizeY );
		m_pMeshIndices.reserve( nScrGridSizeX * nScrGridSizeY );

		// Grid vertex generation
		for( int32 y(0); y< nScrGridSizeY; ++y )
		{      
			vv.y = (float) y * fRcpScrGridSizeY;// + fRcpScrGridSize;

			for( int32 x(0); x< nScrGridSizeX; ++x )
			{
				// vert 1
				vv.x = (float) x * fRcpScrGridSizeX;// + fRcpScrGridSize;

				// store in z edges information
				float fx = fabs((vv.x)*2.0f - 1.0f);
				float fy = fabs((vv.y)*2.0f - 1.0f);
				//float fEdgeDisplace = cry_sqrtf(fx*fx + fy * fy);//max(fx, fy);
				float fEdgeDisplace = max(fx,fy);
				//cry_sqrtf(fx*fx + fy * fy);
				vv.z = fEdgeDisplace; //!((y==0 ||y == nScrGridSize-1) || (x==0 || x == nScrGridSize-1));

				int32 n = m_pMeshVerts.Count();
				tmp.xyz = vv;
				m_pMeshVerts.Add( tmp );        
			}
		}

		if( bUseTessHW )
		{
			// Normal approach
			int32 nIndex = 0;
			for( int32 y(0); y< nScrGridSizeY - 1; ++y )
			{      
				for( int32 x(0); x< nScrGridSizeX - 1; ++x, ++nIndex )
				{        
					m_pMeshIndices.Add( nScrGridSizeX * y + x );
					m_pMeshIndices.Add( nScrGridSizeX * y + x + 1);
					m_pMeshIndices.Add( nScrGridSizeX * (y + 1) + x); 

					m_pMeshIndices.Add( nScrGridSizeX * (y + 1) + x); 
					m_pMeshIndices.Add( nScrGridSizeX * y + x + 1);
					m_pMeshIndices.Add( nScrGridSizeX * (y + 1) + x + 1); 
										
					//m_pMeshIndices.Add( nIndex );
					//m_pMeshIndices.Add( nIndex + 1);
					//m_pMeshIndices.Add( nIndex + nScrGridSizeX); 

					//m_pMeshIndices.Add( nIndex + nScrGridSizeX); 
					//m_pMeshIndices.Add( nIndex + 1);
					//m_pMeshIndices.Add( nIndex + nScrGridSizeX + 1); 
				}
			}
		}
		else
		{
			// Grid index generation

			if( swathWidth <= 0){
				// Normal approach
				int32 nIndex = 0;
				for( int32 y(0); y< nScrGridSizeY - 1; ++y )
				{      
					for( int32 x(0); x< nScrGridSizeX; ++x, ++nIndex )
					{        
						m_pMeshIndices.Add( nIndex );
						m_pMeshIndices.Add( nIndex + nScrGridSizeX); 
					}

					if( nScrGridSizeY - 2 > y)
					{
						m_pMeshIndices.Add( nIndex + nScrGridSizeY -1); 
						m_pMeshIndices.Add( nIndex ); 
					}
				}
			}else{
				// Boustrophedonic walk
				//
				//  0  1  2  3  4 
				//  5  6  7  8  9
				// 10 11 12 13 14
				// 15 16 17 18 19
				//
				// Should generate the following indices
				// 0 5 1 6 2 7 3 8 4 9 9 14 14 9 13 8 12 7 11 6 10 5 5 10 10 15 11 16 12 17 13 18 14 19 
				//

				int32 startX = 0, endX = swathWidth-1;

				do{

					for( int32 y(0); y < nScrGridSizeY - 1; y += 2 )
					{
						// Forward
						for( int32 x(startX); x <= endX; ++x )
						{
							m_pMeshIndices.Add( y * nScrGridSizeX + x );
							m_pMeshIndices.Add( (y + 1) * nScrGridSizeX + x ); 
						}

						// Can we go backwards?
						if( y + 2 < nScrGridSizeY ) {
							// Restart strip by duplicating last and first of next strip
							m_pMeshIndices.Add( (y + 1) * nScrGridSizeX + endX ); 
							m_pMeshIndices.Add( (y + 2) * nScrGridSizeX + endX ); 

							//Backward
							for( int32 x(endX); x >= startX; --x )
							{
								m_pMeshIndices.Add( (y + 2) * nScrGridSizeX + x );
								m_pMeshIndices.Add( (y + 1) * nScrGridSizeX + x ); 
							}

							// Restart strip
							if(y + 2 == nScrGridSizeY - 1 && endX < nScrGridSizeX-1){
								if(endX < nScrGridSizeX-1){
									// Need to restart at the top of the next column
									m_pMeshIndices.Add( (nScrGridSizeY - 1) * nScrGridSizeX + startX ); 
									m_pMeshIndices.Add( endX );
								}
							}else{
								m_pMeshIndices.Add( (y + 1) * nScrGridSizeX + startX ); 
								m_pMeshIndices.Add( (y + 2) * nScrGridSizeX + startX ); 
							}
						}else{
							// We can restart to next column
							if(endX < nScrGridSizeX-1){ 
								// Restart strip for next swath
								m_pMeshIndices.Add( (nScrGridSizeY - 1) * nScrGridSizeX + endX ); 
								m_pMeshIndices.Add( endX );
							}
						}
					}	

					startX = endX;
					endX   = startX + swathWidth-1;

					if(endX >= nScrGridSizeX) endX = nScrGridSizeX-1;

				}while(startX < nScrGridSizeX-1);

			}
		}

		m_nVertsCount = m_pMeshVerts.Count();
		m_nIndicesCount = m_pMeshIndices.Count();

		if(m_pRenderMesh)
			GetRenderer()->DeleteRenderMesh(m_pRenderMesh);

		m_pRenderMesh = GetRenderer()->CreateRenderMeshInitialized( 
			m_pMeshVerts.GetElements(), 
			m_pMeshVerts.Count(), 
			eVF_P3F_C4B_T2F, 
			m_pMeshIndices.GetElements(), 
			m_pMeshIndices.Count(), 
			bUseTessHW? prtTriangleList : prtTriangleStrip,
			"OutdoorWaterGrid","OutdoorWaterGrid",
			eRMT_Static);

		m_pRenderMesh->SetChunk(m_pMaterial, 0, m_pMeshVerts.Count(), 0, m_pMeshIndices.Count(), 1.0f);  

		if( m_bOceanFFT )
			m_pOceanRE->Create(m_pMeshVerts.Count(), m_pMeshVerts.GetElements(), m_pMeshIndices.Count(), m_pMeshIndices.GetElements());

		m_pMeshVerts.Free();
		m_pMeshIndices.Free();
	}
}

void COcean::Render(const int32 nRecurseLevel, int32 nFrameID)
{
	FUNCTION_PROFILER_3DENGINE;

	// if reaches render stage - means ocean is visible

	C3DEngine * p3DEngine = (C3DEngine*)Get3DEngine();  
	IRenderer* pRenderer( GetRenderer() );

	int32 nBufID = (nFrameID & 1);
	Vec3 vCamPos = GetCamera().GetPosition();
	float fWaterLevel = p3DEngine->GetWaterLevel();
 
	CRenderObject * pObject = GetRenderer()->EF_GetObject_Temp();
	if (!pObject)
		return;
	pObject->m_II.m_Matrix.SetIdentity();
	pObject->m_pRenderNode = this;
	pObject->m_fDistance = GetTerrain()->GetDistanceToSectorWithWater();
	
	m_fLastFov = GetCamera().GetFov(); 

	// make distance to water level near to zero
	m_pRenderMesh->SetBBox(vCamPos, vCamPos);

	// test for multiple lights and shadows support

	SRenderObjData *pOD = pRenderer->EF_GetObjData(pObject, true);

	PodArray<CDLight*> * pSources = m_p3DEngine->GetDynamicLightSources();
	if( pSources->Count() && (pSources->GetAt(0)->m_Flags & DLF_SUN) )
	{
		pObject->m_DynLMMask[m_nRenderThreadListID] = 1;
		pObject->m_ObjFlags |= FOB_INSHADOW;
		if( GetCVars()->e_Shadows && GetCVars()->e_ShadowsOnWater )
		{
			AABB oceanBox;
			float fWaterPlaneSize = GetCamera().GetFarPlane();
			oceanBox.min = Vec3( vCamPos.x-fWaterPlaneSize, vCamPos.y-fWaterPlaneSize,fWaterLevel-0.1f);
			oceanBox.max = Vec3( vCamPos.x+fWaterPlaneSize, vCamPos.y+fWaterPlaneSize,fWaterLevel+0.1f);
			const uint64 nShadowCasters = 
				Get3DEngine()->GetObjManager()->GetShadowFrustumsList(
				NULL, oceanBox, 0, pObject->m_DynLMMask[m_nRenderThreadListID], true);
			pOD->m_ShadowCasters = nShadowCasters;
			pObject->m_bHasShadowCasters = nShadowCasters != 0;
		}
	}

	m_Camera = GetCamera();
	pOD->m_pCustomCamera = &m_Camera;
	pObject->m_fAlpha = 1.f;//m_fWaterTranspRatio;

	m_fRECustomData[0] = p3DEngine->m_oceanWindDirection;
	m_fRECustomData[1] = p3DEngine->m_oceanWindSpeed;
	m_fRECustomData[2] = p3DEngine->m_oceanWavesSpeed;
	m_fRECustomData[3] = p3DEngine->m_oceanWavesAmount;
	m_fRECustomData[4] = p3DEngine->m_oceanWavesSize;

	cry_sincosf(p3DEngine->m_oceanWindDirection, &m_fRECustomData[6], &m_fRECustomData[5]);
	m_fRECustomData[7] = fWaterLevel;
	m_fRECustomData[8] = m_fRECustomData[9] = m_fRECustomData[10] = m_fRECustomData[11] = 0.0f;

	bool isFastpath =GetCVars()->e_WaterOcean == 1;  
	bool bUsingMergedFog = false;
	if( isFastpath )
	{
		Vec3 camPos( GetCamera().GetPosition() );
    
		// Check if we outside water volume - we can enable fast path with merged fog version 
		if( camPos.z - fWaterLevel >= p3DEngine->m_oceanWavesSize )
		{   
			Vec3 cFinalFogColor = 
				gEnv->p3DEngine->GetSunColor().CompMul(m_p3DEngine->m_oceanFogColor);
			Vec4 vFogParams = Vec4(
				10.0f * cFinalFogColor * m_p3DEngine->m_oceanFogColorMultiplier,
				m_p3DEngine->m_oceanFogDensity * 1.44269502f);// log2(e) = 1.44269502

			m_fRECustomData[8] = vFogParams.x;
			m_fRECustomData[9] = vFogParams.y; 
			m_fRECustomData[10] = vFogParams.z;
			m_fRECustomData[11] = vFogParams.w;
			bUsingMergedFog = true;
		}
	}

	if(CMatInfo * pMatInfo = (CMatInfo *)(IMaterial*)m_pMaterial)
	{
		float fInstanceDistance = GetTerrain()->GetDistanceToSectorWithWater();
		pMatInfo->PrecacheMaterial(fInstanceDistance, 0, false);
	}

	if( !GetCVars()->e_WaterOceanFFT || !m_bOceanFFT  )
	{
		m_pRenderMesh->SetREUserData(&m_fRECustomData[0]);
		m_pRenderMesh->AddRenderElements(m_pMaterial, pObject, EFSLIST_WATER, 0);
	}
	else
	{
		SShaderItem& shaderItem( m_pMaterial->GetShaderItem( 0 ) );
		m_pOceanRE->m_CustomData = &m_fRECustomData[0];
		pRenderer->EF_AddEf( m_pOceanRE, shaderItem, pObject, EFSLIST_WATER, 0);
	}

	if( GetCVars()->e_WaterOceanBottom )
		RenderBottomCap(nRecurseLevel);    

	if( !bUsingMergedFog )
		RenderFog(); 
}

void COcean::RenderBottomCap(const int32 nRecurseLevel)
{
	C3DEngine * p3DEngine = (C3DEngine*)Get3DEngine();  

	Vec3 vCamPos = GetCamera().GetPosition();
	float fWaterLevel = p3DEngine->GetWaterLevel();

	// Render ocean with screen space tessellation

	int32 nScreenY = GetRenderer()->GetHeight();
	int32 nScreenX = GetRenderer()->GetWidth();

	if(!nScreenY || !nScreenX)
		return;

	// Calculate water geometry and update vertex buffers
	int32 nScrGridSize = 5;  
	float fRcpScrGridSize = 1.0f / (float) nScrGridSize;

	if( !m_pBottomCapVerts.Count() || !m_pBottomCapIndices.Count() ||  nScrGridSize * nScrGridSize != m_pBottomCapVerts.Count() )
	{

		m_pBottomCapVerts.Clear();
		m_pBottomCapIndices.Clear();

		SVF_P3F_C4B_T2F tmp;
		Vec3 vv;
		vv.z = 0;

		// Grid vertex generation
		for( int32 y(0); y< nScrGridSize; ++y )
		{
			vv.y = (float) y * fRcpScrGridSize + fRcpScrGridSize;
			for( int32 x(0); x< nScrGridSize; ++x )
			{
				vv.x = (float) x * fRcpScrGridSize + fRcpScrGridSize;
				tmp.xyz = vv;
				m_pBottomCapVerts.Add( tmp );        
			}
		}

		// Normal approach
		int32 nIndex = 0;
		for( int32 y(0); y< nScrGridSize - 1; ++y )
		{      
			for( int32 x(0); x< nScrGridSize; ++x, ++nIndex )
			{        
				m_pBottomCapIndices.Add( nIndex );
				m_pBottomCapIndices.Add( nIndex + nScrGridSize); 
			}

			if( nScrGridSize - 2 > y)
			{
				m_pBottomCapIndices.Add( nIndex + nScrGridSize -1); 
				m_pBottomCapIndices.Add( nIndex ); 
			}
		}

		if(m_pBottomCapRenderMesh)
			GetRenderer()->DeleteRenderMesh(m_pBottomCapRenderMesh);

		m_pBottomCapRenderMesh = GetRenderer()->CreateRenderMeshInitialized( 
			m_pBottomCapVerts.GetElements(), 
			m_pBottomCapVerts.Count(), 
			eVF_P3F_C4B_T2F, 
			m_pBottomCapIndices.GetElements(), 
			m_pBottomCapIndices.Count(), 
			prtTriangleStrip,
			"OceanBottomGrid","OceanBottomGrid",
			eRMT_Static);

		m_pBottomCapRenderMesh->SetChunk(m_pBottomCapMaterial, 0, m_pBottomCapVerts.Count(), 0, m_pBottomCapIndices.Count(), 1.0f);
	}

	CRenderObject * pObject = GetRenderer()->EF_GetObject_Temp();
	if (!pObject)
		return;
	pObject->m_II.m_Matrix.SetIdentity();
	pObject->m_pRenderNode = this;

	// make distance to water level near to zero
	m_pBottomCapRenderMesh->SetBBox(vCamPos, vCamPos);

	SRenderObjData *pOD = GetRenderer()->EF_GetObjData(pObject, true);
	m_Camera = GetCamera();
	pOD->m_pCustomCamera = &m_Camera;
	pObject->m_fAlpha = 1.f;
	pObject->m_fDistance = GetTerrain()->GetDistanceToSectorWithWater();

	m_pBottomCapRenderMesh->AddRenderElements(m_pBottomCapMaterial, pObject, EFSLIST_GENERAL, 0); 
}

void COcean::RenderFog()
{
	if( !GetCVars()->e_Fog || !GetCVars()->e_FogVolumes )
		return;

	IRenderer* pRenderer( GetRenderer() );
	C3DEngine* p3DEngine( Get3DEngine() );

	int fillThreadID = (int32)GetRenderer()->EF_Query(EFQ_MainThreadList);

	CRenderObject* pROVol( pRenderer->EF_GetObject_Temp() );
	if (!pROVol)
		return;

	bool isFastpath =GetCVars()->e_WaterOcean==1;  
	bool isLowSpec( GetCVars()->e_ObjQuality == CONFIG_LOW_SPEC || isFastpath);

	if (pROVol && m_pWVRE[fillThreadID] && ( (!isLowSpec && m_pFogIntoMat && m_pFogOutofMat) || 
		(isLowSpec && m_pFogIntoMatLowSpec && m_pFogOutofMatLowSpec)))
	{
		Vec3 camPos( GetCamera().GetPosition() );
		float waterLevel( p3DEngine->GetWaterLevel() );
		Vec3 planeOrigin( camPos.x, camPos.y, waterLevel );

		// fill water volume param structure
		m_wvParams[fillThreadID].m_center = planeOrigin;
		m_wvParams[fillThreadID].m_fogPlane.Set( Vec3( 0, 0, 1 ), -waterLevel );

		float distCamToFogPlane( camPos.z + m_wvParams[fillThreadID].m_fogPlane.d );
		m_wvParams[fillThreadID].m_viewerCloseToWaterPlane = ( distCamToFogPlane ) < 0.5f;
		m_wvParams[fillThreadID].m_viewerInsideVolume = distCamToFogPlane < 0.00f;
		m_wvParams[fillThreadID].m_viewerCloseToWaterVolume = true;

		if( !isFastpath || (distCamToFogPlane < p3DEngine->m_oceanWavesSize) )
		{
			if (isLowSpec)
			{
				m_wvParams[fillThreadID].m_fogColor = 10.0f * m_p3DEngine->m_oceanFogColor * m_p3DEngine->m_oceanFogColorMultiplier;
				m_wvParams[fillThreadID].m_fogDensity = m_p3DEngine->m_oceanFogDensity;

				m_wvoParams[fillThreadID].m_fogColor = Vec3( 0, 0, 0 ); // not needed for low spec
				m_wvoParams[fillThreadID].m_fogColorShallow = Vec3( 0, 0, 0 ); // not needed for low spec
				m_wvoParams[fillThreadID].m_fogDensity = 0; // not needed for low spec

				m_pWVRE[fillThreadID]->m_pOceanParams = 0;
			}
			else
			{
				m_wvParams[fillThreadID].m_fogColor = Vec3( 0, 0, 0 ); // not needed, we set ocean specific params below
				m_wvParams[fillThreadID].m_fogDensity = 0; // not needed, we set ocean specific params below

				m_wvoParams[fillThreadID].m_fogColor = m_p3DEngine->m_oceanFogColor * m_p3DEngine->m_oceanFogColorMultiplier;
				m_wvoParams[fillThreadID].m_fogColorShallow = m_p3DEngine->m_oceanFogColorShallow * m_p3DEngine->m_oceanFogColorMultiplier;
				m_wvoParams[fillThreadID].m_fogDensity = m_p3DEngine->m_oceanFogDensity;

				m_pWVRE[fillThreadID]->m_pOceanParams = &m_wvoParams[fillThreadID];
			}
   	        
			// tessellate plane
			float planeSize( 2.0f * GetCamera().GetFarPlane() ); 
			size_t subDivSize( min(64, 1 + (int32) (planeSize / 512.0f)) );
			if( isFastpath )
				subDivSize = 4;

			size_t numSubDivVerts( (subDivSize + 1) * (subDivSize + 1) );

			if( m_wvVertices[fillThreadID].size() != numSubDivVerts )
			{
				m_wvVertices[fillThreadID].resize( numSubDivVerts );
				m_wvParams[fillThreadID].m_pVertices = &m_wvVertices[fillThreadID][0];
				m_wvParams[fillThreadID].m_numVertices = m_wvVertices[fillThreadID].size();

				m_wvIndices[fillThreadID].resize( subDivSize * subDivSize * 6 );
				m_wvParams[fillThreadID].m_pIndices = &m_wvIndices[fillThreadID][0];
				m_wvParams[fillThreadID].m_numIndices = m_wvIndices[fillThreadID].size();

				size_t ind(0);
				for( uint32 y(0); y < subDivSize; ++y )
				{
					for( uint32 x(0); x < subDivSize; ++x, ind += 6 )
					{
						m_wvIndices[fillThreadID][ind+0] = (y  ) * (subDivSize+1) + (x  );
						m_wvIndices[fillThreadID][ind+1] = (y  ) * (subDivSize+1) + (x+1);
						m_wvIndices[fillThreadID][ind+2] = (y+1) * (subDivSize+1) + (x+1);

						m_wvIndices[fillThreadID][ind+3] = (y  ) * (subDivSize+1) + (x  );
						m_wvIndices[fillThreadID][ind+4] = (y+1) * (subDivSize+1) + (x+1);
						m_wvIndices[fillThreadID][ind+5] = (y+1) * (subDivSize+1) + (x  );
					}
				}
			}
			{				
				float xyDelta(2.0f * planeSize / (float) subDivSize);
				float zDelta(waterLevel - camPos.z);

				size_t ind(0);
				float yd(-planeSize);
				for (uint32 y(0); y <= subDivSize; ++y, yd += xyDelta)
				{
					float xd(-planeSize);
					for (uint32 x(0); x <= subDivSize; ++x, xd += xyDelta, ++ind)
					{
						m_wvVertices[fillThreadID][ind].xyz = Vec3(xd, yd, zDelta);
						m_wvVertices[fillThreadID][ind].st = Vec2(0, 0); 
					}
				}
			}

			// fill in data for render object
			pROVol->m_II.m_Matrix.SetIdentity();
			pROVol->m_fSort = 0;

			// get shader item
			SShaderItem& shaderItem(m_wvParams[fillThreadID].m_viewerInsideVolume ? 
				(isLowSpec ? m_pFogOutofMatLowSpec->GetShaderItem(0) : m_pFogOutofMat->GetShaderItem(0)) : 
				(isLowSpec ? m_pFogIntoMatLowSpec->GetShaderItem(0) : m_pFogIntoMat->GetShaderItem(0)));

			// add to renderer
			pRenderer->EF_AddEf(m_pWVRE[fillThreadID], shaderItem, pROVol, EFSLIST_WATER_VOLUMES, distCamToFogPlane < -0.1f);
		}
	}
}

bool COcean::IsVisible(int32 nFrameID)
{
	if( abs(m_nLastVisibleFrameId - nFrameID) <= 2 )
		m_fLastVisibleFrameTime = 0.0f;

	ITimer* pTimer(gEnv->pTimer);
	m_fLastVisibleFrameTime += gEnv->pTimer->GetFrameTime();

	if( m_fLastVisibleFrameTime > 2.0f ) // at least 2 seconds
		return (abs(m_nLastVisibleFrameId - nFrameID)<64); // and at least 64 frames

	return true; // keep water visible for a couple frames - or at least 1 second - minimizes popping during fast camera movement
}

void COcean::SetTimer(ITimer *pTimer)
{
	assert(pTimer);
	m_pTimer = pTimer;
}

float COcean::GetWave( const Vec3 &pPos, int32 nFrameID )
{
	// todo: optimize...

	IRenderer* pRenderer( GetRenderer() );
	if( !pRenderer  )
		return 0.0f;

	EShaderQuality nShaderQuality = pRenderer->EF_GetShaderQuality( eST_Water );

	if( !m_pTimer || nShaderQuality < eSQ_High )
		return 0.0f;

	// Return height - matching computation on GPU

	C3DEngine* p3DEngine( Get3DEngine() );

	bool bOceanFFT = false;
	if( ( pRenderer->GetFeatures() & (RFT_HW_VERTEXTEXTURES) && GetCVars()->e_WaterOceanFFT) && nShaderQuality >= eSQ_High)
		bOceanFFT = true;

	if( bOceanFFT) 
	{
		Vec4 pDispPos = Vec4(0,0,0, 0);

		if( m_pOceanRE ) 
		{
			// Get height from FFT grid

			Vec4 *pGridFFT = m_pOceanRE->GetDisplaceGrid();
			if( !pGridFFT )
			return 0.0f; 

			// match scales used in shader
			float fScaleX = pPos.x* 0.0125f * p3DEngine->m_oceanWavesAmount* 1.25f;
			float fScaleY = pPos.y* 0.0125f * p3DEngine->m_oceanWavesAmount* 1.25f;

			float fu = fScaleX * 64.0f;
			float fv = fScaleY * 64.0f;
			int32 u1 = ((int32)fu) & 63;
			int32 v1 = ((int32)fv) & 63;
			int32 u2 = (u1 + 1) & 63;
			int32 v2 = (v1 + 1) & 63;

			// Fractional parts
			float fracu = fu - floorf( fu );
			float fracv = fv - floorf( fv );

			// Get weights
			float w1 = (1 - fracu) * (1 - fracv); 
			float w2 = fracu * (1 - fracv);
			float w3 = (1 - fracu) * fracv;
			float w4 = fracu *  fracv;

			Vec4 h1 = pGridFFT[u1 + v1 * 64];
			Vec4 h2 = pGridFFT[u2 + v1 * 64];
			Vec4 h3 = pGridFFT[u1 + v2 * 64];
			Vec4 h4 = pGridFFT[u2 + v2 * 64];

			// scale and sum the four heights
			pDispPos  = h1 * w1 + h2 * w2 + h3 * w3 + h4 * w4;
		}

		// match scales used in shader
		return pDispPos.z * 0.06f * p3DEngine->m_oceanWavesSize;
	}

	// constant to scale down values a bit
	const float fAnimAmplitudeScale = 1.0f / 5.0f;

	static int32 s_nFrameID = 0; 
	static Vec3 vFlowDir = Vec3(0, 0, 0);  
	static Vec4 vFrequencies = Vec4(0, 0, 0, 0);
	static Vec4 vPhases = Vec4(0, 0, 0, 0);
	static Vec4 vAmplitudes = Vec4(0, 0, 0, 0);

	// Update per-frame data
	if( s_nFrameID != nFrameID )
	{
		cry_sincosf(p3DEngine->m_oceanWindDirection, &vFlowDir.y, &vFlowDir.x);
		vFrequencies = Vec4( 0.233f, 0.455f, 0.6135f, -0.1467f ) * p3DEngine->m_oceanWavesSpeed * 5.0f; 
		vPhases = Vec4(0.1f, 0.159f, 0.557f, 0.2199f) * p3DEngine->m_oceanWavesAmount;
		vAmplitudes = Vec4(1.0f, 0.5f, 0.25f, 0.5f) * p3DEngine->m_oceanWavesSize;

		s_nFrameID = nFrameID;
	}

	float fPhase = cry_sqrtf(pPos.x * pPos.x + pPos.y * pPos.y);
	Vec4 vCosPhase = vPhases * (fPhase + pPos.x);

	Vec4 vWaveFreq = vFrequencies * m_pTimer->GetCurrTime();

	Vec4 vCosWave = Vec4( cry_cosf( vWaveFreq.x * vFlowDir.x + vCosPhase.x ),
	cry_cosf( vWaveFreq.y * vFlowDir.x + vCosPhase.y ),
	cry_cosf( vWaveFreq.z * vFlowDir.x + vCosPhase.z ),
	cry_cosf( vWaveFreq.w * vFlowDir.x + vCosPhase.w ) );


	Vec4 vSinPhase = vPhases * (fPhase + pPos.y);
	Vec4 vSinWave = Vec4( cry_sinf( vWaveFreq.x * vFlowDir.y + vSinPhase.x ),
	cry_sinf( vWaveFreq.y * vFlowDir.y + vSinPhase.y ),
	cry_sinf( vWaveFreq.z * vFlowDir.y + vSinPhase.z ),
	cry_sinf( vWaveFreq.w * vFlowDir.y + vSinPhase.w ) );

	return  ( vCosWave.Dot( vAmplitudes ) + vSinWave.Dot( vAmplitudes ) ) * fAnimAmplitudeScale; 
}

uint32 COcean::GetVisiblePixelsCount()
{
	return m_nVisiblePixelsCount;
}

void COcean::OffsetPosition (const Vec3& delta)
{
#ifdef SEG_WORLD
	if (m_pRNTmpData) m_pRNTmpData->OffsetPosition(delta);
	// CS - TODO: implement
#endif
}
